#!/usr/bin/python3

#import serial
#import serial.tools.list_ports
# from serial.tools import list_ports
import os
import sys
import threading
import time
import json

print ('2024-10-29')
print ('###############################')


class Logging:
    def __init__(self):
        self.log_level = 1
        self.log_outputfile = time.strftime('./log/sci_upgrade_log_%Y%m%d%H%M%S.log', time.localtime(time.time()))
        if not os.path.exists("./log/"):
            os.makedirs("./log/")
        self.filefd =  open(self.log_outputfile, mode='wb')

    def fileoutput(self, str):
        timestr = time.strftime('\n[%Y%m%d%H%M%S]', time.localtime(time.time()))
        self.filefd.write(bytearray(timestr, 'utf-8'))
        self.filefd.write(bytearray(str, 'utf-8'))
        self.filefd.flush()

    def log_str(self, level, str):
        if level < self.log_level:
            print(str)
            self.fileoutput(str)

    def log_buffer_hex(self, level, text, buffer):
        if level < self.log_level:
            str = text + (" ".join( "{:0>2X}".format(item) for item in buffer) ) 
            print(str)
            self.fileoutput(str)


log = Logging()

# print ('参数个数为:', len(sys.argv), '个参数')
# print ('参数列表:', str(sys.argv))
# print ('脚本名:', str(sys.argv[0]))
# print ('###############################')

def crc16_RTU(data):
    crc_val = 0xFFFF
    for item in data:
        crc_val = crc_val ^ item
        for i in range(8):
            if crc_val & 1:
                crc_val >>= 1
                crc_val ^= 0xA001
            else:
                crc_val >>= 1
    return crc_val # Modbus(RTU)

def bitrev(poly, width):
    var = 0
    for i in range(width):
        if poly & 0x00000001 :
            var |= 1 << (width - 1 - i)
        poly >>= 1
    return var

crc32_table = []

def crc32_table_init(poly):
    poly = bitrev(poly, 32)
    for i in range(256):
        c = i
        for j in range(8):
            if c & 1:
                c = (poly ^ (c >> 1) )
            else:
                c >>= 1
        crc32_table.append(c)

def  crc32_step(crc_init, buffer):
    index = 0
    for item in buffer:
        index = 0x000000FF & ( crc_init ^ item )
        crc_init = (crc_init >> 8) ^ crc32_table[index]
    return crc_init

def  crc32_single(buffer):
    crc_init = 0xffffffff
    crc_init = crc32_step(crc_init, buffer)
    return crc_init ^ 0xffffffff


class SignatureInfo:
    def __init__(self):
        self.file_magic = 0
        self.file_version = 0
        self.file_valid = 0
        self.fw_length = 0
        self.fw_crc32 = 0
        self.fw_chip_id = 0
        self.fw_product_id = 0
        self.fw_version = 0
        self.fw_build_datetime = 0
        self.fw_sign_datetime = 0
        self.sign_crc32 = 0
    
    def Unpack(self, buffer: bytearray):
        pos = 0
        self.file_magic     =  ( buffer[pos + 0] | (buffer[pos + 1] << 8) | (buffer[pos + 2] << 16) | (buffer[pos + 3] << 24) )
        pos += 4
        self.file_version   =  ( buffer[pos + 0] | (buffer[pos + 1] << 8) )
        pos += 2
        self.file_valid     =  ( buffer[pos + 0] | (buffer[pos + 1] << 8) )
        pos += 2
        self.fw_length      =  ( buffer[pos + 0] | (buffer[pos + 1] << 8)  | (buffer[pos + 2] << 16)  | (buffer[pos + 3] << 24) )
        pos += 4
        self.fw_crc32       =  ( buffer[pos + 0] | (buffer[pos + 1] << 8)  | (buffer[pos + 2] << 16)  | (buffer[pos + 3] << 24) )
        pos += 4
        self.fw_chip_id     =  ( buffer[pos + 0] | (buffer[pos + 1] << 8)  | (buffer[pos + 2] << 16)  | (buffer[pos + 3] << 24) )
        pos += 4
        self.fw_product_id  =  ( buffer[pos + 0] | (buffer[pos + 1] << 8)  | (buffer[pos + 2] << 16)  | (buffer[pos + 3] << 24) )
        pos += 4
        self.fw_version     =  ( buffer[pos + 0] | (buffer[pos + 1] << 8)  | (buffer[pos + 2] << 16)  | (buffer[pos + 3] << 24) )
        pos += 4
        self.fw_build_datetime  =  ( buffer[pos + 0] | (buffer[pos + 1] << 8)  | (buffer[pos + 2] << 16)  | (buffer[pos + 3] << 24) )
        pos += 4
        self.fw_sign_datetime   =  ( buffer[pos + 0] | (buffer[pos + 1] << 8)  | (buffer[pos + 2] << 16)  | (buffer[pos + 3] << 24) )
        pos += 4
        pos = 1020
        self.sign_crc32     =  ( buffer[pos + 0] | (buffer[pos + 1] << 8)  | (buffer[pos + 2] << 16)  | (buffer[pos + 3] << 24) )

    def Pack(self)->bytearray:
        buffer = bytearray(b'')
        pos = 0
        buffer.append( (self.file_magic >> 0) & 0x000000FF )
        buffer.append( (self.file_magic >> 8) & 0x000000FF )
        buffer.append( (self.file_magic >> 16) & 0x000000FF )
        buffer.append( (self.file_magic >> 24) & 0x000000FF )
        pos += 4
        buffer.append( (self.file_version >> 0) & 0x000000FF )
        buffer.append( (self.file_version >> 8) & 0x000000FF )
        pos += 2
        buffer.append( (self.file_valid >> 0) & 0x000000FF )
        buffer.append( (self.file_valid >> 8) & 0x000000FF )
        pos += 2
        buffer.append( (self.fw_length >> 0) & 0xFF )
        buffer.append( (self.fw_length >> 8) & 0xFF )
        buffer.append( (self.fw_length >> 16) & 0xFF )
        buffer.append( (self.fw_length >> 24) & 0xFF )
        pos += 4
        buffer.append( (self.fw_crc32 >> 0) & 0xFF )
        buffer.append( (self.fw_crc32 >> 8) & 0xFF )
        buffer.append( (self.fw_crc32 >> 16) & 0xFF )
        buffer.append( (self.fw_crc32 >> 24) & 0xFF )
        pos += 4
        buffer.append( (self.fw_chip_id >> 0) & 0xFF )
        buffer.append( (self.fw_chip_id >> 8) & 0xFF )
        buffer.append( (self.fw_chip_id >> 16) & 0xFF )
        buffer.append( (self.fw_chip_id >> 24) & 0xFF )
        pos += 4
        buffer.append( (self.fw_product_id >> 0) & 0xFF )
        buffer.append( (self.fw_product_id >> 8) & 0xFF )
        buffer.append( (self.fw_product_id >> 16) & 0xFF )
        buffer.append( (self.fw_product_id >> 24) & 0xFF )
        pos += 4
        buffer.append( (self.fw_version >> 0) & 0xFF )
        buffer.append( (self.fw_version >> 8) & 0xFF )
        buffer.append( (self.fw_version >> 16) & 0xFF )
        buffer.append( (self.fw_version >> 24) & 0xFF )
        pos += 4
        buffer.append( (self.fw_build_datetime >> 0) & 0xFF )
        buffer.append( (self.fw_build_datetime >> 8) & 0xFF )
        buffer.append( (self.fw_build_datetime >> 16) & 0xFF )
        buffer.append( (self.fw_build_datetime >> 24) & 0xFF )
        pos += 4
        buffer.append( (self.fw_sign_datetime >> 0) & 0xFF )
        buffer.append( (self.fw_sign_datetime >> 8) & 0xFF )
        buffer.append( (self.fw_sign_datetime >> 16) & 0xFF )
        buffer.append( (self.fw_sign_datetime >> 24) & 0xFF )
        pos += 4
        while (pos < 1020):
            buffer.append(0x00)
            pos += 1
        pos = 1020
        hash = 0xFFFFFFFF
        hash = crc32_step( hash, buffer)
        self.sign_crc32 = hash ^ 0xFFFFFFFF
        buffer.append( (self.sign_crc32 >> 0) & 0xFF )
        buffer.append( (self.sign_crc32 >> 8) & 0xFF )
        buffer.append( (self.sign_crc32 >> 16) & 0xFF )
        buffer.append( (self.sign_crc32 >> 24) & 0xFF )
        return buffer


class FirmwareSignature:
    def __init__(self):
        self.cal_sign_crc32 = 0
        self.cal_fw_crc32 = 0
        self.cal_fw_len = 0
        self.fw_length = 0
        self.fw_crc32 = 0
        self.fwfile_path = ""
        self.signfile_path = ""
        self.SignInfo = object()
        self.result = 0
    
    def SignatureFilePack(self, SignInfo: SignatureInfo):
        log.log_str(0, "########################")
        log.log_str(0, "SignatureFilePack()" )
        log.log_str(0, "fwfile:"+ self.fwfile_path )
        log.log_str(0, "signfile:"+ self.signfile_path )
        self.result = 0

        self.fw_length = os.path.getsize(self.fwfile_path)
        if self.fw_length <= 0 :
            log.log_str(0, "fw_length error <= 0")
            self.result = 0
            return
        
        self.fw_crc32 = self.get_file_crc32(self.fwfile_path)
        if self.fw_crc32 <= 0 :
            log.log_str(0, "fw_crc32 error <= 0")
            self.result = 0
            return 
        
        log.log_str(0, "fw_crc32 {:08X} ".format( self.fw_crc32) )
        log.log_str(0, "fw_length  {:08X} {:d} ".format( self.fw_length, self.fw_length) )
        
        self.SignInfo = SignInfo
        SignInfo.file_magic     = 0x4641544F
        # SignInfo.file_version   = 0x0001
        # SignInfo.file_valid     = 0x0001
        SignInfo.fw_length      = self.fw_length
        SignInfo.fw_crc32       = self.fw_crc32
        # SignInfo.fw_chip_id     = 0x11223344
        # SignInfo.fw_product_id  = 0x11223344
        # SignInfo.fw_version     = 0x11223344
        # SignInfo.fw_build_datetime = 0x11223344
        SignInfo.fw_sign_datetime  = int(time.time()) #time.localtime(time.time()).count
        self.SignInfoBuffer        = SignInfo.Pack()

        with open(self.signfile_path, 'wb') as sf:
            sf.write(self.SignInfoBuffer)
            sf.flush()
            fwdata_offset = 0
            fwdata_len = 0
            with open(self.fwfile_path, 'rb') as fh:
                while True:
                    fwdata = fh.read(1024)
                    if len(fwdata) > 0:
                        fwdata_len = len(fwdata)
                        sf.write(fwdata)
                        sf.flush()
                        fwdata_offset += fwdata_len
                    else:
                        log.log_str(0, "copy ok")
                        log.log_str(0, "file len {:08X} ".format( fwdata_offset) )
                        break
            sf.close()
        self.result = 1

    def SignatureFileUnpack(self)->SignatureInfo: 
        SignInfo = SignatureInfo()
        log.log_str(0, "########################")
        log.log_str(0, "SignatureFileUnpack()" )
        log.log_str(0, "signfile:"+ self.signfile_path )
        log.log_str(0, "fwfile:"+ self.fwfile_path )
        self.result = 0

        self.signfile_len = os.path.getsize(self.signfile_path)
        if self.signfile_len <= 0 :
            log.log_str(0, "signfile_len error <= 0")
            self.result = 0
            return SignInfo
        
        self.signfile_crc32 = self.get_file_crc32(self.signfile_path)
            
        log.log_str(0, "signfile_len {:08X} ".format(self.signfile_len) )
        log.log_str(0, "signfile_crc32 {:08X} ".format(self.signfile_crc32) )
        
        self.cal_sign_crc32 = self.get_file_signinfo_crc32(self.signfile_path)
        if self.cal_sign_crc32 <= 0 :
            log.log_str(0, "cal_sign_crc32 error <= 0")
            self.result = 0
            return SignInfo
        
        self.cal_fw_crc32 = self.get_file_fw_crc32(self.signfile_path)
        if self.cal_fw_crc32 <= 0 :
            log.log_str(0, "cal_fw_crc32 error <= 0")
            self.result = 0
            return SignInfo
        
        self.cal_fw_len = self.signfile_len - 1024
        
        log.log_str(0, "###################" )

        with open(self.signfile_path, 'rb') as fh:
            SignData = fh.read(1024)
            SignInfo.Unpack(SignData)

            log.log_str(0, "file_magic {:04X}".format(SignInfo.file_magic) )
            if SignInfo.file_magic != 0x4641544F :
                log.log_str(0, "file_magic error {:08X}".format(SignInfo.file_magic) )
                self.result = 0
                return SignInfo
            
            log.log_str(0, "sign_crc32 {:08X} {:08X}".format(SignInfo.sign_crc32, self.cal_sign_crc32) )
            if SignInfo.sign_crc32 != self.cal_sign_crc32 :
                log.log_str(0, "sign_crc32 error {:08X} {:08X}".format(SignInfo.sign_crc32, self.cal_sign_crc32) )
                self.result = 0
                return SignInfo
            
            log.log_str(0, "fw_crc32   {:08X} {:08X}".format(SignInfo.fw_crc32, self.cal_fw_crc32) )
            if SignInfo.fw_crc32 != self.cal_fw_crc32 :
                log.log_str(0, "fw_crc32 error {:08X} {:08X}".format(SignInfo.fw_crc32, self.cal_fw_crc32) )
                self.result = 0
                return SignInfo
        
            log.log_str(0, "fw_len  {:d}  {:d} ".format(SignInfo.fw_length, self.cal_fw_len) )
            if SignInfo.fw_length != self.cal_fw_len :
                log.log_str(0, "fw_length error {:d} != {:d} ".format(SignInfo.fw_length, self.cal_fw_len) )
                self.result = 0
                return SignInfo
        
            with open(self.fwfile_path, 'wb') as sf:
                fwdata_offset = 0
                fwdata_len = 0
                with open(self.signfile_path, 'rb') as fh:
                    fh.read(1024) 
                    while True:
                        fwdata = fh.read(4096) 
                        if len(fwdata) > 0:
                            fwdata_len = len(fwdata)
                            sf.write(fwdata)
                            sf.flush()
                            fwdata_offset += fwdata_len
                        else:
                            log.log_str(0, "copy ok")
                            log.log_str(0, "file len {:08X} ".format( fwdata_offset) )
                            break
                sf.close()
        self.result = 1
        return SignInfo

    # 获取文件crc32
    def get_file_crc32(self, filepath):
        hash = 0xFFFFFFFF
        with open(filepath, 'rb') as fh:
            while True:
                s = fh.read(65535)
                if not s:
                    break
                hash = crc32_step( hash, s)
        return hash ^ 0xFFFFFFFF
    
    # 获取文件签名信息crc32
    def get_file_signinfo_crc32(self, filepath):
        hash = 0xFFFFFFFF
        with open(filepath, 'rb') as fh:
            s = fh.read(1020)
            hash = crc32_step( hash, s)
        return hash ^ 0xFFFFFFFF

    # 获取文件签名信息crc32
    def get_file_fw_crc32(self, filepath):
        hash = 0xFFFFFFFF
        with open(filepath, 'rb') as fh:
            s = fh.read(1024)
            while True:
                s = fh.read(65535)
                if not s:
                    break
                hash = crc32_step( hash, s)
        return hash ^ 0xFFFFFFFF


def  crc_test():
    crc_val = crc16_RTU(bytearray(b"\x12\x34\x56\x78\x90") )
    print(hex(crc_val))
    hash = crc32_single( b"\x12\x34\x56\x78\x90"  )
    print(hex(hash))
    hash = 0xFFFFFFFF
    hash = crc32_step(hash,  b"\x12\x34\x56\x78" )
    hash = crc32_step(hash,  b"\x90" )
    hash = hash ^ 0xFFFFFFFF
    print(hex(hash))
    print( '{:08X} {:08X} {:08X} {:d}'.format( hash, hash, hash, hash) )

def json_init(jsonfilepath)->object:
    data = {
        "SignInfo":{
            "file_magic": 0,
            "file_version": 0,
            "file_valid": 0,
            "fw_chip_id": 0,
            "fw_product_id": 0,
            "fw_version": 0,
            "fw_build_datetime": 0
        },
        "mode":0,
        "fwfile":"fwfile.bin",
        "signfile":"signfile.ota"
    }
    with open(jsonfilepath, 'w') as f:
        json.dump(data, f, indent=4)
        f.flush()
    return data

def json_load(jsonfilepath)->object:
    data = object()
    try:
        f = open(jsonfilepath, 'r') 
    except IOError:
        log.log_str( 0, "config init" )
        data = json_init(jsonfilepath)
    else:
        log.log_str( 0, "config load" )
        data = json.load(f)
    return data


if __name__ == '__main__':
    crc32_table_init(0x04c11db7)
    # crc_test()

    time_start = time.time()
    str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))
    log.log_str( 0, str )

    SignInfo = SignatureInfo()
    fw_sign = FirmwareSignature()

    jsonfilepath = "FirmwareSignature.json"
    if len(sys.argv) >= 2:
        jsonfilepath = sys.argv[1]

    config = json_load(jsonfilepath)
    if (config["mode"] == 1):
        log.log_str( 0, "pack" )
        fw_sign.fwfile_path = config["fwfile"]
        fw_sign.signfile_path = config["signfile"]
        SignInfo.file_magic   = config["SignInfo"]["file_magic"]
        SignInfo.file_version = config["SignInfo"]["file_version"]
        SignInfo.file_valid   = config["SignInfo"]["file_valid"]
        SignInfo.fw_chip_id   = config["SignInfo"]["fw_chip_id"]
        SignInfo.fw_product_id     = config["SignInfo"]["fw_product_id"]
        SignInfo.fw_version        = config["SignInfo"]["fw_version"]
        SignInfo.fw_build_datetime = config["SignInfo"]["fw_build_datetime"]
        fw_sign.SignatureFilePack(SignInfo)
        log.log_str(0, "###################" )
        log.log_str(0, "file_magic    0x{:08X} ".format( SignInfo.file_magic) )
        log.log_str(0, "file_version  0x{:08X} ".format( SignInfo.file_version) )
        log.log_str(0, "file_valid    0x{:08X} ".format( SignInfo.file_valid) )
        log.log_str(0, "fw_length     0x{:08X} ".format( SignInfo.fw_length) )
        log.log_str(0, "fw_crc32      0x{:08X} ".format( SignInfo.fw_crc32) )
        log.log_str(0, "fw_chip_id    0x{:08X} ".format( SignInfo.fw_chip_id) )
        log.log_str(0, "fw_product_id 0x{:08X} ".format( SignInfo.fw_product_id) )
        log.log_str(0, "fw_version    0x{:08X} ".format( SignInfo.fw_version) )
        log.log_str(0, "fw_build_datetime  0x{:08X} ".format( SignInfo.fw_build_datetime) )
        log.log_str(0, "fw_sign_datetime   0x{:08X} ".format( SignInfo.fw_sign_datetime) )
        log.log_str(0, "result   0x{:08X} ".format( fw_sign.result) )
    elif (config["mode"] == 2):
        log.log_str( 0, "unpack" )
        fw_sign.fwfile_path = config["fwfile"]
        fw_sign.signfile_path = config["signfile"]
        SignInfo = fw_sign.SignatureFileUnpack()
        log.log_str(0, "###################" )
        log.log_str(0, "file_magic    0x{:08X} ".format( SignInfo.file_magic) )
        log.log_str(0, "file_version  0x{:08X} ".format( SignInfo.file_version) )
        log.log_str(0, "file_valid    0x{:08X} ".format( SignInfo.file_valid) )
        log.log_str(0, "fw_length     0x{:08X} ".format( SignInfo.fw_length) )
        log.log_str(0, "fw_crc32      0x{:08X} ".format( SignInfo.fw_crc32) )
        log.log_str(0, "fw_chip_id    0x{:08X} ".format( SignInfo.fw_chip_id) )
        log.log_str(0, "fw_product_id 0x{:08X} ".format( SignInfo.fw_product_id) )
        log.log_str(0, "fw_version    0x{:08X} ".format( SignInfo.fw_version) )
        log.log_str(0, "fw_build_datetime  0x{:08X} ".format( SignInfo.fw_build_datetime) )
        log.log_str(0, "fw_sign_datetime   0x{:08X} ".format( SignInfo.fw_sign_datetime) )
        log.log_str(0, "result   0x{:08X} ".format( fw_sign.result) )
    else:
        log.log_str( 0, "none" )

    if len(sys.argv)>= 3:
        log.log_level = 256

    time_end = time.time()
    str = 'time {:f} - {:f} = {:f}'.format(time_end, time_start, time_end - time_start)
    log.log_str(0, str ) 
    str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))
    log.log_str(0, str )

